// Global variables
let isActive = false;
let isCssMode = false;
let highlightedElement = null;
let highlightOverlay = null;
let tooltipElement = null;
let highlightedElements = [];
let highlightOverlays = [];
const excludedAttributes = [
    "id",
    "class",
    "style",
    "href",
    "target",
    "onclick",
    "src",
    "onload",
    "onunload",
    "onbeforeunload",
    "onresize",
    "onscroll",
    "onhashchange",
    "onpopstate",
    "onfocus",
    "onblur",
    "onerror",
    "onafterprint",
    "onbeforeprint",
    "onoffline",
    "ononline",
    "onchange",
    "oninput",
    "oninvalid",
    "onreset",
    "onselect",
    "onsubmit",
    "onkeydown",
    "onkeypress",
    "onkeyup",
    "ondblclick",
    "onmousedown",
    "onmousemove",
    "onmouseout",
    "onmouseover",
    "onmouseup",
    "onmousewheel",
    "onwheel",
    "oncontextmenu",
    "onabort",
    "oncanplay",
    "oncanplaythrough",
    "onended",
    "onloadeddata",
    "onloadedmetadata",
    "onloadstart",
    "onpause",
    "onplay",
    "onplaying",
    "onprogress",
    "onratechange",
    "onseeked",
    "onseeking",
    "onstalled",
    "onsuspend",
    "ontimeupdate",
    "onvolumechange",
    "onwaiting",
    "ontoggle",
    "onanimationstart",
    "onanimationend",
    "onanimationiteration",
    "ontransitionend"
];

// Check if the extension can run on the current page
function canRunOnPage() {
    // Check if we're in a regular web page (not a Chrome internal page)
    if (
        window.location.protocol === "chrome:" ||
        window.location.protocol === "chrome-extension:" ||
        window.location.protocol === "about:"
    ) {
        return false;
    }

    // Check if document is accessible
    try {
        // Try to access the document
        const test = document.body;
        return true;
    } catch (error) {
        return false;
    }
}

// Initialize the extension
function initializeExtension() {
    chrome.runtime.sendMessage({action: "clearXPaths"});
    // Check if we can run on this page
    if (!canRunOnPage()) {
        return;
    }

    // Check if we're in an iframe
    const isInIframe = window !== window.top;

    // If we're in the main document, create the UI elements
    if (!isInIframe) {
        // Create highlight overlay if it doesn't exist
        if (!highlightOverlay) {
            highlightOverlay = document.createElement("div");
            highlightOverlay.className = "xpath-finder-highlight";
            highlightOverlay.style.display = "none";
            document.body.appendChild(highlightOverlay);
        }

        // Create tooltip element if it doesn't exist
        if (!tooltipElement) {
            tooltipElement = document.createElement("div");
            tooltipElement.className = "xpath-finder-tooltip";
            tooltipElement.style.display = "none";
            document.body.appendChild(tooltipElement);
        }
    }
}

// Initialize when the DOM is fully loaded
if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initializeExtension);
} else {
    initializeExtension();
}

window.addEventListener('resize', function() {
    const newHighlightedElements = Array.from(highlightedElements);
    clearHighlightedElements();
    if (newHighlightedElements.length > 0) {
        for (let i = 0; i < newHighlightedElements.length; i++) {
            const element = newHighlightedElements[i];
            highlightMatchedElement(element);
        }
    }
});

// Initialize message listener
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    try {
        // Check if we can run on this page
        if (!canRunOnPage()) {
            sendResponse({
                success: false,
                error: "Cannot run on this page",
                canRun: false,
            });
            return true;
        }

        if (request.action === "activate") {
            // Set CSS mode if provided
            if (request.isCssMode !== undefined) {
                isCssMode = request.isCssMode;
            }
            activateElementSelection();
            sendResponse({
                success: true,
                message: "Element selection activated",
                canRun: true,
                isCssMode: isCssMode // Return current CSS mode state
            });
        } else if (request.action === "deactivate") {
            deactivateElementSelection();
            sendResponse({
                success: true,
                message: "Element selection deactivated",
                canRun: true,
            });
        } else if (request.action === "getStatus") {
            sendResponse({isActive: isActive, canRun: true});
        } else if (request.action === "setCssMode") {
            isCssMode = request.isCssMode;
            sendResponse({success: true, isCssMode: isCssMode});
        } else if (request.action === "searchXPathAndCss") {            
            const result = searchAndHighlightXPathAndCss(request.xpathAndCss);
            sendResponse(result);
        }else if (request.action === "clearHighlightedElements") {
            clearHighlightedElements();
            sendResponse({success: true});
        }
    } catch (error) {
        console.error("XPath-CSS Finder: Error handling message", error);
        sendResponse({success: false, error: error.message, canRun: false});
    }

    return true; // Keep the message channel open for asynchronous responses
});

// Activate element selection mode
function activateElementSelection() {
    if (isActive) return;
    isActive = true;

    // Add crosshair cursor to body
    document.body.classList.add("xpath-finder-active");
    // Add event listeners to the document
    document.addEventListener("mouseover", handleMouseOver, true);
    document.addEventListener("mouseout", handleMouseOut, true);
    document.addEventListener("click", handleElementClick, true);

    // Add event listeners to all accessible iframes
    const iframes = document.querySelectorAll("iframe");
    iframes.forEach((iframe) => {
        try {
            // This will throw an error for cross-origin iframes
            const iframeDoc = iframe.contentDocument;

            // Add event listeners to the iframe document
            iframeDoc.addEventListener("mouseover", handleMouseOver, true);
            iframeDoc.addEventListener("mouseout", handleMouseOut, true);
            iframeDoc.addEventListener("click", handleElementClick, true);
        } catch (error) {
        }
    });

    // Make sure the overlay and tooltip are created
    if (!highlightOverlay) {
        highlightOverlay = document.createElement("div");
        highlightOverlay.className = "xpath-finder-highlight";
        document.body.appendChild(highlightOverlay);
    }

    highlightOverlay.style.display = "none";

    if (!tooltipElement) {
        tooltipElement = document.createElement("div");
        tooltipElement.className = "xpath-finder-tooltip";
        document.body.appendChild(tooltipElement);
    }

    tooltipElement.style.display = "none";
}

// Search for elements matching XPath or Css and highlight them
function searchAndHighlightXPathAndCss(expression) {
    try {
        // Clear any existing highlights from previous searches
        clearHighlightedElements();

        try {
            const elements = window.parent.document.querySelectorAll(expression);
            const count = elements.length;
            // Highlight each matching element
            for (let i = 0; i < elements.length; i++) {
                const element = elements[i];
                highlightMatchedElement(element);
            }
            return { success: true, count: count };
        }catch  (error){
            // Evaluate the XPath expression
            const result = window.parent.document.evaluate(
                expression,
                window.parent.document,
                null,
                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                null
            );

            const count = result.snapshotLength;
            if (count === 0) {
                return { success: false, error: 0 }; 
            }

            // Highlight each matching element
            for (let i = 0; i < count; i++) {
                const element = result.snapshotItem(i);
                highlightMatchedElement(element);
            }

            return { success: true, count: count };
        }
    } catch (error) {
        return { success: false, error: 0 };
    }
}

// Highlight a matched element
function highlightMatchedElement(element) {
    if (!element) return;

    // Create a new highlight overlay
    const overlay = document.createElement("div");
    overlay.className = "xpath-finder-highlight";
    document.body.appendChild(overlay);

    // Position the overlay over the element
    const rect = element.getBoundingClientRect();
    overlay.style.display = "block";
    overlay.style.top = window.scrollY + rect.top + "px";
    overlay.style.left = window.scrollX + rect.left + "px";
    overlay.style.width = rect.width + "px";
    overlay.style.height = rect.height + "px";

    // Add to the list of highlighted elements and overlays
    highlightedElements.push(element);
    highlightOverlays.push(overlay);
}

// Clear all highlighted elements
function clearHighlightedElements() {
    // Remove all highlight overlays
    for (const overlay of highlightOverlays) {
        if (overlay && overlay.parentNode) {
            overlay.parentNode.removeChild(overlay);
        }
    }

    // Clear the arrays
    highlightedElements = [];
    highlightOverlays = [];
}

// Deactivate element selection mode
function deactivateElementSelection() {
    if (!isActive) return;
    isActive = false;

    // Remove crosshair cursor from body
    document.body.classList.remove("xpath-finder-active");

    // Remove event listeners from the document
    document.removeEventListener("mouseover", handleMouseOver, true);
    document.removeEventListener("mouseout", handleMouseOut, true);
    document.removeEventListener("click", handleElementClick, true);

    // Remove event listeners from all accessible iframes
    const iframes = document.querySelectorAll("iframe");
    iframes.forEach((iframe) => {
        try {
            // This will throw an error for cross-origin iframes
            const iframeDoc = iframe.contentDocument;

            // Remove event listeners from the iframe document
            iframeDoc.removeEventListener("mouseover", handleMouseOver, true);
            iframeDoc.removeEventListener("mouseout", handleMouseOut, true);
            iframeDoc.removeEventListener("click", handleElementClick, true);
        } catch (error) {
            // Cross-origin iframe, can't access contentDocument
        }
    });

    // Hide the highlight overlay and tooltip
    if (highlightOverlay) {
        highlightOverlay.style.display = "none";
    }

    // Hide tooltip
    if (tooltipElement) {
        tooltipElement.style.display = "none";
    }

    // Clear the highlighted element
    highlightedElement = null;

    // Clear any highlighted elements from XPath search
    clearHighlightedElements();
}

// Handle mouseover event
function handleMouseOver(event) {
    if (!isActive) return;

    // Update highlighted element
    highlightedElement = event.target;

    // Update highlight overlay position
    updateHighlightOverlay(highlightedElement);

    // Update tooltip
    updateTooltip(event, highlightedElement);
}

// Handle mouseout event
function handleMouseOut(event) {
    if (!isActive) return;

    // Check if mouse is leaving the document
    if (
        event.relatedTarget === null ||
        event.relatedTarget.nodeName === "HTML" ||
        !document.documentElement.contains(event.relatedTarget)
    ) {
        // Hide highlight overlay and tooltip
        if (highlightOverlay) {
            highlightOverlay.style.display = "none";
        }

        if (tooltipElement) {
            tooltipElement.style.display = "none";
        }
    }
}

// Update highlight overlay position and size
function updateHighlightOverlay(element) {
    if (!element || !highlightOverlay) return;

    try {
        const rect = element.getBoundingClientRect();

        // For elements in iframes, we need to adjust the position
        if (isElementInIframe(element)) {
            // Find the iframe that contains this element
            const elementDocument = element.ownerDocument;
            const iframes = document.querySelectorAll("iframe");

            for (let i = 0; i < iframes.length; i++) {
                try {
                    if (iframes[i].contentDocument === elementDocument) {
                        // Found the iframe, now adjust the position
                        const iframeRect = iframes[i].getBoundingClientRect();

                        highlightOverlay.style.display = "block";
                        highlightOverlay.style.top =
                            window.scrollY + iframeRect.top + rect.top + "px";
                        highlightOverlay.style.left =
                            window.scrollX + iframeRect.left + rect.left + "px";
                        highlightOverlay.style.width = rect.width + "px";
                        highlightOverlay.style.height = rect.height + "px";
                        return;
                    }
                } catch (e) {
                    // Cross-origin iframe, can't access contentDocument
                    continue;
                }
            }

            // If we can't find the iframe, hide the overlay
            highlightOverlay.style.display = "none";
            return;
        }

        // Regular handling for elements in the main document
        highlightOverlay.style.display = "block";
        highlightOverlay.style.top = window.scrollY + rect.top + "px";
        highlightOverlay.style.left = window.scrollX + rect.left + "px";
        highlightOverlay.style.width = rect.width + "px";
        highlightOverlay.style.height = rect.height + "px";
    } catch (error) {
        console.error("XPath-CSS Finder: Error updating highlight overlay:", error);
        highlightOverlay.style.display = "none";
    }
}

// Update tooltip position and content
function updateTooltip(event, element) {
    if (!element || !tooltipElement) return;

    // Get element tag name and truncated XPath
    const tagName = element.tagName.toLowerCase();
    const shortXPath = getShortXPath(element);

    // Set tooltip content
    tooltipElement.textContent = `${tagName} - ${shortXPath}`;

    // Position tooltip near the cursor
    tooltipElement.style.display = "block";

    // For elements in iframes, we need to adjust the position
    if (isElementInIframe(element)) {
        // Find the iframe that contains this element
        const elementDocument = element.ownerDocument;
        const iframes = document.querySelectorAll("iframe");

        for (let i = 0; i < iframes.length; i++) {
            try {
                if (iframes[i].contentDocument === elementDocument) {
                    // Found the iframe, now adjust the position
                    const iframeRect = iframes[i].getBoundingClientRect();

                    tooltipElement.style.top = iframeRect.top + event.clientY + 15 + "px";
                    tooltipElement.style.left =
                        iframeRect.left + event.clientX + 10 + "px";
                    break;
                }
            } catch (e) {
                // Cross-origin iframe, can't access contentDocument
                continue;
            }
        }
    } else {
        // Regular handling for elements in the main document
        tooltipElement.style.top = event.clientY + 15 + "px";
        tooltipElement.style.left = event.clientX + 10 + "px";
    }

    // Ensure tooltip stays within viewport
    const tooltipRect = tooltipElement.getBoundingClientRect();
    if (tooltipRect.right > window.innerWidth) {
        tooltipElement.style.left =
            window.innerWidth - tooltipRect.width - 10 + "px";
    }
    if (tooltipRect.bottom > window.innerHeight) {
        tooltipElement.style.top = event.clientY - tooltipRect.height - 10 + "px";
    }
}

// Get a short version of XPath for the tooltip
function getShortXPath(element) {
    // Check if element is inside an iframe
    const isInIframe = isElementInIframe(element);

    // Try to get a short, readable XPath for the tooltip
    if (element.id) {
        if (isInIframe) {
            // For elements with ID inside iframe, include a simplified iframe indicator
            return `iframe → //*[@id="${element.id}"]`;
        }
        return `//*[@id="${element.id}"]`;
    }

    // Check for direct text content
    const directText = getDirectTextContent(element);
    if (directText && directText.length > 0 && directText.length < 30) {
        // Use direct text content if it's short enough
        if (isInIframe) {
            return `iframe → //${element.tagName.toLowerCase()}[text()="${directText}"]`;
        }
        return `//${element.tagName.toLowerCase()}[text()="${directText}"]`;
    }

    // Special handling for SELECT elements
    if (element.tagName === "SELECT") {
        // If it has a name attribute, use that
        if (element.name) {
            if (isInIframe) {
                return `iframe → //select[@name="${element.name}"]`;
            }
            return `//select[@name="${element.name}"]`;
        }

        // If it has a first option with text, use that
        if (
            element.options &&
            element.options.length > 0 &&
            element.options[0].text
        ) {
            if (isInIframe) {
                return `iframe → //option[text()="${element.options[0].text}"]/parent::select`;
            }
            return `//option[text()="${element.options[0].text}"]/parent::select`;
        }
    }

    // Get a short path with tag names and positions
    let path = "";
    let current = element;
    let parent = current.parentElement;
    let depth = 0;

    // Get the document that contains this element
    const elementDocument = element.ownerDocument;

    while (current && parent && depth < 2) {
        let position = 1;
        let sibling = current.previousElementSibling;

        while (sibling) {
            if (sibling.tagName === current.tagName) {
                position++;
            }
            sibling = sibling.previousElementSibling;
        }

        path = `/${current.tagName.toLowerCase()}[${position}]${path}`;
        current = parent;
        parent = current.parentElement;
        depth++;
    }

    if (isInIframe) {
        return `iframe → ...${path}`;
    }
    return `...${path}`;
}

// Handle element click event
function handleElementClick(event) {
    if (!isActive) return;
    clearHighlightedElements();
    // Prevent default click behavior
    event.preventDefault();
    event.stopPropagation();

    // Generate both XPaths and CSS selectors for the clicked element
    const element = event.target;

    // Always generate both types of selectors
    const xpathSelectors = generateXPaths(element);
    const cssSelectors = generateCssSelectors(element);

    // Use the appropriate selectors based on current mode
    const selectors = isCssMode ? cssSelectors : xpathSelectors;
    const selectorType = isCssMode ? "CSS selectors" : "XPaths";
    const selectorCount = selectors.length;

    // Log if no selectors were generated
    if (selectorCount === 0) {
        console.warn(
            `XPath-CSS Finder: No ${selectorType} were generated for the element`,
            element
        );
    }

    // Send message to popup or background script with both selector types
    try {
        chrome.runtime.sendMessage(
            {
                action: "elementSelected",
                xpaths: selectors,
                xpathSelectors: xpathSelectors,  // Store both types
                cssSelectors: cssSelectors,      // Store both types
                xpathCount: selectorCount,
                elementTag: element.tagName.toLowerCase(),
                elementHasEmptyXPaths: selectorCount === 0,
                isCssMode: isCssMode
            }
        );
    } catch (error) {
        console.error("XPath-CSS Finder: Error sending message:", error);
    }

    return false;
}

// Generate multiple CSS selectors for an element
function generateCssSelectors(element) {
    const selectors = [];

    // 1. ID-based selectors
    const idSelectors = getCssSelectorWithId(element);
    idSelectors.forEach((selector) => {
        selectors.push({
            type: "ID-based",
            xpath: selector.selector,
            description: selector.description,
        });
    });

    // 2. Class-based selectors
    const classSelectors = getCssSelectorWithClass(element);
    classSelectors.forEach((selector) => {
        selectors.push({
            type: "Class-based",
            xpath: selector.selector,
            description: selector.description,
        });
    });

    // 3. Attribute-based selectors
    const attrSelectors = getCssSelectorWithAttributes(element);
    attrSelectors.forEach((selector) => {
        selectors.push({
            type: "Attribute-based",
            xpath: selector.selector,
            description: selector.description,
        });
    });

    // 4. Optimized selectors
    const optimizedSelectors = getOptimizedCssSelectors(element);
    optimizedSelectors.forEach((selector) => {
        selectors.push({
            type: "Optimized",
            xpath: selector.selector,
            description: selector.description,
        });
    });

    // 5. Position-based selectors
    const positionSelectors = getCssSelectorWithPosition(element);
    positionSelectors.forEach((selector) => {
        selectors.push({
            type: "Position-based",
            xpath: selector.selector,
            description: selector.description,
        });
    });

    // 6. Absolute path selectors
    const absoluteSelector = getAbsoluteCssSelector(element);
    selectors.push({
        type: "Absolute",
        xpath: absoluteSelector,
        description: "Complete CSS path from the root of the document",
    });

    return selectors;
}

// Generate multiple XPath expressions for an element
function generateXPaths(element) {
    const xpaths = [];

    // 1. Absolute XPath
    const absoluteXPath = getAbsoluteXPath(element);
    xpaths.push({
        type: "Absolute",
        xpath: absoluteXPath,
        description: "Complete path from the root of the document",
    });

    // 2. ID-based XPaths
    const idXPaths = getXPathsWithId(element);
    idXPaths.forEach((xpath) => {
        xpaths.push({
            type: "ID-based",
            xpath: xpath.xpath,
            description: xpath.description,
        });
    });

    // 3. Class-based XPaths
    const classXPaths = getXPathsWithClass(element);
    classXPaths.forEach((xpath) => {
        xpaths.push({
            type: "Class-based",
            xpath: xpath.xpath,
            description: xpath.description,
        });
    });

    // 4. Attribute-based XPaths
    const attrXPaths = getXPathsWithAttributes(element);
    attrXPaths.forEach((xpath) => {
        xpaths.push({
            type: "Attribute-based",
            xpath: xpath.xpath,
            description: xpath.description,
        });
    });

    // 5. Optimized XPaths (moved up from position 7 to position 5)
    const optimizedXPaths = getOptimizedXPaths(element);
    optimizedXPaths.forEach((xpath) => {
        xpaths.push({
            type: "Optimized",
            xpath: xpath.xpath,
            description: xpath.description,
        });
    });

    // 6. Text-based XPaths
    const textXPaths = getXPathsWithText(element);
    textXPaths.forEach((xpath) => {
        xpaths.push({
            type: "Text-based",
            xpath: xpath.xpath,
            description: xpath.description,
        });
    });

    // 7. Position-based XPaths
    const positionXPaths = getXPathsWithPosition(element);
    positionXPaths.forEach((xpath) => {
        xpaths.push({
            type: "Position-based",
            xpath: xpath.xpath,
            description: xpath.description,
        });
    });

    return xpaths;
}

// Check if element is inside an iframe
function isElementInIframe(element) {
    try {
        return element.ownerDocument !== document;
    } catch (e) {
        return false;
    }
}

// Get iframe path for an element
function getIframePath(element) {
    try {
        // Find the iframe that contains this element
        const elementDocument = element.ownerDocument;
        const iframes = document.querySelectorAll("iframe");

        for (let i = 0; i < iframes.length; i++) {
            try {
                if (iframes[i].contentDocument === elementDocument) {
                    // Found the iframe, now get its XPath
                    return getAbsoluteXPath(iframes[i]);
                }
            } catch (e) {
            }
        }

        // If we can't find the iframe (might be nested), return a placeholder
        return "//iframe";
    } catch (e) {
        console.error("Error getting iframe path:", e);
        return "//iframe";
    }
}

// Get absolute XPath
function getAbsoluteXPath(element) {
    if (element.nodeType !== Node.ELEMENT_NODE) {
        return "";
    }

    // Check if element is inside an iframe
    const isInIframe = isElementInIframe(element);

    // If in iframe, we need to handle differently
    if (isInIframe) {
        // Get the document that contains this element
        const elementDocument = element.ownerDocument;

        if (element === elementDocument.body) {
            // If it's the body of the iframe document
            return getIframePath(element) + "/html/body";
        }

        let xpath = "";
        let parent = element;

        while (parent && parent !== elementDocument.body) {
            let index = 1;
            let sibling = parent.previousElementSibling;

            while (sibling) {
                if (sibling.tagName === parent.tagName) {
                    index++;
                }
                sibling = sibling.previousElementSibling;
            }

            xpath = `/${parent.tagName.toLowerCase()}[${index}]${xpath}`;
            parent = parent.parentElement;
        }

        // Combine iframe path with element path
        return getIframePath(element) + "/html/body" + xpath;
    }

    // Regular handling for elements in the main document
    if (element === document.body) {
        return "/html/body";
    }

    let xpath = "";
    let parent = element;

    while (parent && parent !== document.body) {
        let index = 1;
        let sibling = parent.previousElementSibling;

        while (sibling) {
            if (sibling.tagName === parent.tagName) {
                index++;
            }
            sibling = sibling.previousElementSibling;
        }

        xpath = `/${parent.tagName.toLowerCase()}[${index}]${xpath}`;
        parent = parent.parentElement;
    }

    return `/html/body${xpath}`;
}

// Get XPaths with ID
function getXPathsWithId(element, needParent = true) {
    const results = [];

    // Direct ID
    if (element.id) {
        // More specific with tag name
        results.push({
            xpath: `//${element.tagName.toLowerCase()}[@id="${element.id}"]`,
            description: "Using element's tag name and ID attribute",
        });
    }

    if (needParent) {
        // Parent with ID
        let parent = element.parentElement;
        let depth = 0;

        while (parent && parent !== document.body && depth < 3) {
            if (parent.id) {
                const tagPath = getRelativePathToParent(element, parent);
                results.push({
                    xpath: `//*[@id="${parent.id}"]${tagPath}`,
                    description: `Relative to parent with ID "${parent.id}"`,
                });
                break;
            }
            parent = parent.parentElement;
            depth++;
        }
    }
    return results;
}

// Get XPaths with class
function getXPathsWithClass(element) {
    const results = [];

    if (element.className && typeof element.className === "string") {
        let xpath;
        let clickedItemClass = element.className;
        let splitClass = clickedItemClass.trim().split(" ");
        if (splitClass.length > 2) {
            let cl = `${splitClass[0]} ${splitClass[1]}`;
            xpath = `//${element.tagName.toLowerCase()}[contains(@class,'${cl}')]`;
            let {count, index} = getElementIndexByXPath(xpath, element);
            if (count > 1) {
                xpath = `(//${element.tagName.toLowerCase()}[contains(@class,'${cl}')])[${index}]`;
            }
        } else {
            xpath = `//${element.tagName.toLowerCase()}[@class="${clickedItemClass}"]`;
            let {count, index} = getElementIndexByXPath(xpath, element);
            if (count > 1) {
                xpath = `(//${element.tagName.toLowerCase()}[@class="${clickedItemClass}"])[${index}]`;
            }
        }
        results.push({
            xpath,
            description: "Using exact class attribute match",
        });

    }

    return results;
}

// Get XPaths with attributes
function getXPathsWithAttributes(element) {
    const results = [];

    // Check all attributes
    for (let i = 0; i < element.attributes.length; i++) {
        const attr = element.attributes[i];
        // Skip id, class, style, href, target, and onclick as they're handled separately or unstable
        if (excludedAttributes.includes(attr.name)) continue;

        if (attr.value) {
            // Exact match
            let xpath = `//${element.tagName.toLowerCase()}[@${attr.name}="${attr.value}"]`;
            let {count, index} = getElementIndexByXPath(xpath, element);
            if (count > 1) {
                xpath = `(//${element.tagName.toLowerCase()}[@${attr.name}="${attr.value}"])[${index}]`
            }
            results.push({
                xpath: xpath,
                description: `Using exact "${attr.name}" attribute`,
            });

            // Contains match for longer attributes
            if (attr.value.length > 10) {
                xpath = `//${element.tagName.toLowerCase()}[contains(@${
                    attr.name
                }, "${attr.value.substring(0, 10)}")]`;
                if (count > 1) {
                    xpath = `(//${element.tagName.toLowerCase()}[contains(@${
                        attr.name
                    }, "${attr.value.substring(0, 10)}")])[${index}]`;
                }
                results.push({
                    xpath,
                    description: `Using partial "${attr.name}" attribute`,
                });
            }
        }
    }

    // Special handling for SELECT elements
    if (element.tagName === "SELECT") {
        // Try to find a good option-based XPath
        if (element.options && element.options.length > 0) {
            // Use the first option text which is often a placeholder like "Select country"
            const firstOption = element.options[0];
            if (firstOption && firstOption.text) {
                results.push({
                    xpath: `//option[text()="${firstOption.text}"]/parent::select`,
                    description: "Using first option text",
                });
            }

            // Count the number of options
            results.push({
                xpath: `//select[count(option)=${element.options.length}]`,
                description: `Using count of options (${element.options.length})`,
            });
        }

        // Try to find a label that's associated with this select
        if (element.id) {
            results.push({
                xpath: `//select[@id="${element.id}"]`,
                description: "Using select ID attribute",
            });

            results.push({
                xpath: `//label[@for="${element.id}"]/following-sibling::select`,
                description: "Using associated label element",
            });
        }
    }

    // Src for images
    if (element.tagName.toLowerCase() === "img" && element.src) {
        const srcValue = element.getAttribute("src");
        if (srcValue) {
            // Use the filename part of the src
            const filename = srcValue.split("/").pop().split("?")[0];
            results.push({
                xpath: `//img[contains(@src, "${filename}")]`,
                description: "Using image filename in src attribute",
            });
        }
    }

    // Data attributes which are often used for testing
    for (let i = 0; i < element.attributes.length; i++) {
        const attr = element.attributes[i];
        if (attr.name.startsWith("data-id")) {
            results.push({
                xpath: `//${element.tagName.toLowerCase()}[@${attr.name}="${
                    attr.value
                }"]`,
                description: `Using "${attr.name}" data attribute`,
            });
        }
    }

    return results;
}

// Get direct text content of an element (excluding child element text)
function getDirectTextContent(element) {
    let text = "";
    let firstTextNodeIndex = Array.from(element.childNodes).findIndex(
        (node) => node.nodeType === Node.TEXT_NODE
    );
    if (firstTextNodeIndex > -1) {
        text = element.childNodes[firstTextNodeIndex].textContent;
    }
    return text;
}

// Get XPaths with text content
function getXPathsWithText(element) {
    const results = [];

    // For non-select elements, proceed with normal text-based XPath generation
    // Get only the direct text content (excluding child elements)
    const directText = getDirectTextContent(element);

    if (directText && directText.length > 0) {
        // For short text, use exact match
        if (directText.length < 50) {
            results.push({
                xpath: `//${element.tagName.toLowerCase()}[text()="${directText}"]`,
                description: "Using exact direct text content match",
            });
        }

        // For any length text, use contains
        // Get first few words for partial match
        const firstWords = directText.slice(0, 5);

        if (directText.length > 10) {
            results.push({
                xpath: `//${element.tagName.toLowerCase()}[contains(text(), "${firstWords}")]`,
                description: "Using partial direct text content match",
            });
        }
    }

    return results;
}

// Get XPaths with position
function getXPathsWithPosition(element) {
    const results = [];

    // Get a relative path with position indices
    let path = "";
    let current = element;
    let parent = current.parentElement;

    // following-sibling xpath
    let previousSiblings = getPreviousSiblings(current);
    if (previousSiblings.length > 0) {
        previousSiblings = previousSiblings.reverse();
        previousSiblings.some((previousSibling) => {

            let result = getXPathsWithId(previousSibling, false);
            if (result.length === 0) {
                result = getXPathsWithText(previousSibling);
            }
            if (result.length === 0) {
                result = getXPathsWithClass(previousSibling);
            }
            if (result.length === 0) {
                result = getXPathsWithAttributes(previousSibling);
            }

            if (result.length > 0) {
                path = result[0]["xpath"];
                let xpath = `${path}/following-sibling::${element.tagName.toLowerCase()}`;
                let {count, index} = getElementIndexByXPath(xpath, element);
                if (count > 1) {
                    xpath = `(${path}/following-sibling::${element.tagName.toLowerCase()})[${index}]`;
                    if (count === index) {
                        xpath = `(${path}/following-sibling::${element.tagName.toLowerCase()})[last()]`;
                    }
                }
                results.push({
                    xpath: xpath,
                    description: `Following-sibling xpath by ${path}`,
                });
                return true;
            }
            return false;
        });
    }

    // preceding-sibling xpath
    let nextSiblings = getNextSiblings(current);
    if (nextSiblings.length > 0) {
        nextSiblings.some((nextSibling) => {
            let result = getXPathsWithId(nextSibling);
            if (result.length === 0) {
                result = getXPathsWithText(nextSibling);
            }
            if (result.length === 0) {
                result = getXPathsWithClass(nextSibling);
            }
            if (result.length === 0) {
                result = getXPathsWithAttributes(nextSibling);
            }
            if (result.length > 0) {
                path = result[0]["xpath"];
                let xpath = `${path}/preceding-sibling::${element.tagName.toLowerCase()}`;
                let {count, index} = getElementIndexByXPath(xpath, element);
                if (count > 1) {
                    xpath = `(${path}/preceding-sibling::${element.tagName.toLowerCase()})[${index}]`;
                    if (count === index) {
                        xpath = `(${path}/preceding-sibling::${element.tagName.toLowerCase()})[last()]`;
                    }
                }

                results.push({
                    xpath: xpath,
                    description: `Preceding-sibling xpath by ${path}`,
                });
                return true;
            }
            return false;
        });
    }

    // special case for span
    if (current.tagName.toLowerCase() === "span") {
        let result = getXPathsWithText(current);
        if (result.length === 0) {
            result = getXPathsWithClass(current);
        }
        if (result.length === 0) {
            result = getXPathsWithAttributes(current);
        }
        if (result.length > 0) {
            path = result[0]["xpath"];
            let xpath = `${path}/ancestor::button`;
            let {count, index} = getElementIndexByXPath(xpath, current);
            if (count > 1) {
                xpath = `(${path}/ancestor::button)[${index}]`;
                if (count === index) {
                    xpath = `(${path}/ancestor::button)[last()]`;
                }
                results.push({
                    xpath: xpath,
                    description: `Ancestor button xpath by ${path}`,
                });
            }
        }
    }

    // use a relative path from a unique parent
    path = "";
    current = element;
    parent = current.parentElement;

    let result;
    if (parent && parent !== document.body) {
        result = getXPathsWithId(parent, false);
        if (result.length === 0) {
            result = getXPathsWithText(parent);
        }
        if (result.length === 0) {
            result = getXPathsWithClass(parent);
        }
        if (result.length === 0) {
            result = getXPathsWithAttributes(parent);
        }
        if (result.length > 0) {
            path = result[0]["xpath"];
            let {sibLingPosition} = getElementPositionInParent(
                element,
                parent
            );
            // Add the element tag name to the end of the path to make it more specia
            results.push({
                xpath: `${path}/${element.tagName.toLowerCase()}[${sibLingPosition}]`,
                description: "Relative path from a unique parent",
            });
        }
    }

    // Simple position among siblings
    let position = 1;
    let elementInfo = getElementInfo(element);
    if (elementInfo["totalElements"] > 1) {
        path = `(//${element.tagName.toLowerCase()})[${elementInfo["position"]}]`;
    } else {
        path = `//${element.tagName.toLowerCase()}`;
    }

    results.push({
        xpath: path,
        description: `${elementInfo["position"]}${getOrdinalSuffix(
            position
        )} ${element.tagName.toLowerCase()} element among siblings`,
    });

    // Relative path from parent
    if (parent && parent !== document.body) {
        const parentTag = parent.tagName.toLowerCase();
        let elementInfo = getElementInfo(parent);
        let {sibLingPosition} = getElementPositionInParent(
            element,
            parent
        );
        if (elementInfo["totalElements"] > 1 && sibLingPosition > 1) {
            path = `(//${parentTag})[${
                elementInfo["position"]
            }]/${element.tagName.toLowerCase()}[${sibLingPosition}]`;
        } else if (elementInfo["totalElements"] > 1 && sibLingPosition === 1) {
            path = `(//${parentTag})[${
                elementInfo["position"]
            }]/${element.tagName.toLowerCase()}[${sibLingPosition}]`;
        } else {
            path = `//${parentTag}/${element.tagName.toLowerCase()}[${sibLingPosition}]`;
        }

        // Position within parent
        results.push({
            xpath: path,
            description: `Direct child of ${parentTag} element`,
        });
    }
    return results;
}

// Get optimized XPaths (shortest unique paths)
function getOptimizedXPaths(element) {
    const results = [];

    // Try to find unique combinations

    let xPathsWithId = getXPathsWithId(element);
    results.push(...xPathsWithId);

    // 1. Tag + attribute combinations
    for (let i = 0; i < element.attributes.length; i++) {
        const attr = element.attributes[i];
        if (!excludedAttributes.includes(attr.name)) {
            const xpath = `//${element.tagName.toLowerCase()}[@${attr.name}="${
                attr.value
            }"]`;
            try {
                if (
                    document.evaluate(
                        xpath,
                        document,
                        null,
                        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                        null
                    ).snapshotLength === 1
                ) {
                    results.push({
                        xpath: xpath,
                        description: `Unique combination of tag and ${attr.name} attribute`,
                    });
                }
            } catch (e) {
                // Skip invalid XPath expressions
            }
        }
    }

    // 2. Tag + class combinations for each class
    let xPathsWithClass = getXPathsWithClass(element);
    results.push(...xPathsWithClass);

    // 3. Tag + text for elements with short text
    const directText = getDirectTextContent(element);
    if (directText && directText.length > 0) {
        if (directText.length < 30) {
            const xpath = `//${element.tagName.toLowerCase()}[text()="${directText}"]`;
            try {
                if (
                    document.evaluate(
                        xpath,
                        document,
                        null,
                        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                        null
                    ).snapshotLength === 1
                ) {
                    results.push({
                        xpath: xpath,
                        description: "Unique combination of tag and direct text content",
                    });
                }
            } catch (e) {
                // Skip invalid XPath expressions
            }

            // Also try with contains for more flexibility
            const containsXpath = `//${element.tagName.toLowerCase()}[contains(text(), "${directText}")]`;
            try {
                if (
                    document.evaluate(
                        containsXpath,
                        document,
                        null,
                        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                        null
                    ).snapshotLength === 1 &&
                    !results.some((r) => r.xpath === xpath) // Don't add if exact match already added
                ) {
                    results.push({
                        xpath: containsXpath,
                        description:
                            "Unique combination of tag and partial direct text content",
                    });
                }
            } catch (e) {
                // Skip invalid XPath expressions
            }
        }
    }

    // 4. Try with parent context for more uniqueness
    if (element.parentElement && element.parentElement !== document.body) {
        const parent = element.parentElement;
        const parentTag = parent.tagName.toLowerCase();

        // Position within parent
        let position = 1;
        let sibling = element.previousElementSibling;

        while (sibling) {
            if (sibling.tagName === element.tagName) {
                position++;
            }
            sibling = sibling.previousElementSibling;
        }

        // Parent with class + child with position
        if (parent.className && typeof parent.className === "string") {
            const classes = parent.className.trim().split(/\s+/);
            for (const cls of classes) {
                if (cls.length > 0) {
                    const xpath = `//${parentTag}[contains(@class, "${cls}")]/${element.tagName.toLowerCase()}[${position}]`;
                    try {
                        if (
                            document.evaluate(
                                xpath,
                                document,
                                null,
                                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                                null
                            ).snapshotLength === 1
                        ) {
                            results.push({
                                xpath: xpath,
                                description: `Unique path using parent's class "${cls}" and position`,
                            });
                        }
                    } catch (e) {
                        // Skip invalid XPath expressions
                    }
                }
            }
        }
    }

    let xPathsWithPosition = getXPathsWithPosition(element);
    results.push(...xPathsWithPosition.slice(0, 2));
    return results;
}

// Get relative path from element to parent
function getRelativePathToParent(element, parent) {
    let path = "";
    let current = element;

    while (current && current !== parent) {
        let position = 1;
        let sibling = current.previousElementSibling;

        while (sibling) {
            if (sibling.tagName === current.tagName) {
                position++;
            }
            sibling = sibling.previousElementSibling;
        }

        path = `/${current.tagName.toLowerCase()}[${position}]${path}`;
        current = current.parentElement;
    }

    return path;
}

// Helper function to get ordinal suffix
function getOrdinalSuffix(n) {
    const s = ["th", "st", "nd", "rd"];
    const v = n % 100;
    return s[(v - 20) % 10] || s[v] || s[0];
}

function getElementInfo(element) {
    const tagName = element.tagName.toLowerCase();
    const allSameTagElements = document.querySelectorAll(tagName);
    const elementsArray = Array.from(allSameTagElements);

    return {
        tagName: tagName,
        totalElements: elementsArray.length,
        position: elementsArray.indexOf(element) + 1,
    };
}

function getElementPositionInParent(element, parent) {
    const tagName = element.tagName.toLowerCase();
    const sameTagChildren = Array.from(parent.children).filter(
        child => child.tagName.toLowerCase() === tagName
    );
    const sibLingPosition = sameTagChildren.indexOf(element) + 1;
    return {
        parentTagName: parent.tagName.toLowerCase(),
        sibLingPosition: sibLingPosition,
    };
}

function getPreviousSiblings(element) {
    const siblings = [];
    let sibling = element.previousElementSibling;

    while (sibling) {
        siblings.unshift(sibling);
        sibling = sibling.previousElementSibling;
    }

    return siblings;
}

function getNextSiblings(element) {
    const siblings = [];
    let sibling = element.nextElementSibling;

    while (sibling) {
        siblings.push(sibling);
        sibling = sibling.nextElementSibling;
    }

    return siblings;
}

function getElementIndexByXPath(xpath, element) {
    try {
        const result = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        const snapshotLength = result.snapshotLength;

        if (snapshotLength === 0) {
            return {count: 0, index: -1};
        }

        for (let i = 0; i < snapshotLength; i++) {
            const node = result.snapshotItem(i);
            if (node === element) {
                return {count: snapshotLength, index: i + 1};
            }
        }

        return {count: snapshotLength, index: -1};
    } catch (error) {
        console.error("XPath evaluation error:", error);
        return {count: -1, index: -1, error: error.message};
    }
}

